// This is the main DLL file.

#include "cvm_net.h"

#pragma unmanaged 
extern "C" {
	#include "../cvm_c/constants.h"
	#include "../cvm_c/cvm.c"
}
#pragma managed

namespace CVM
{	
	System::String^ Op::GetName(OpCode opcode) {
		switch (opcode)
		{
		#define OPCODE(OP, DESC) case OpCode::Op_##OP: return #OP;
		#include "../cvm_c/opcodes.h"
		}
		return "_unknown_";
	}

	System::String^ Op::GetDesc(OpCode opcode) {
		switch (opcode)
		{
		#define OPCODE(OP, DESC) case OpCode::Op_##OP: return #DESC;
		#include "../cvm_c/opcodes.h"
		}
		return "_unknown_";
	}

	int Op::GetNumBytes(OpCode opcode) {
		switch (opcode)
		{
		case CVM::OpCode::Op_klong:
        case CVM::OpCode::Op_long:
            return 5;
        case CVM::OpCode::Op_kshort:
        case CVM::OpCode::Op_short:
        case CVM::OpCode::Op_swapNM:
            return 3;
        case CVM::OpCode::Op_kbyte:
        case CVM::OpCode::Op_byte:
        case CVM::OpCode::Op_getN:
        case CVM::OpCode::Op_setN:
        case CVM::OpCode::Op_swapN:
        case CVM::OpCode::Op_digN:
        case CVM::OpCode::Op_buryN:
            return 2;
        default:
            return 1;
		}
	}

	System::String^ Op::ToString() {
		System::Text::StringBuilder^ sb = gcnew System::Text::StringBuilder();
		sb->Append(Name);
		if (NumBytes > 1) {
			for (int j = 1; j < NumBytes; ++j) {
				sb->Append("#");
				sb->Append(Byte[j]);
			}
		}
		return sb->ToString();
	}

	/* Object functions */

	int Object::Count::get() { 
		return array_count(obj);
	}

	Val Object::operator[](int n) {
		return Val(array_at(obj, n));
	}

	array<byte>^ Object::ArrayBytes::get() {
		int n = Count * 4;
		array<byte>^ r = gcnew array<byte>(n);
		byte* ptr = (byte*)array_begin(obj);
		for (int i=0; i < n; ++i)
			r[i] = *ptr++;
		return r;
	}

	void Object::Push(Val v) {
		array_push(obj, v.val);
	}

	void Object::Pop() {
		array_pop(obj);
	}

	System::String^ Object::TypeString::get() {
		switch (Type)
		{	
		case ObjectType: return "object";
		case FloatType: return "float";
		case FunType: return "function";
		case ContType: return "continuation";
		case TableType: return "table";
		case StoreType: return "store";
		case BlobType: return "blob";
		case IntType: return "int";
		default: return "unknown";
		}
	}

	/* KeyValue functions */

	uint KeyValue::Key::get() {
		return key;
	}

	Val KeyValue::Value::get() {
		return Val(val);
	}

	/* Table functions */

	Val Table::operator[](uint key) {
		::KeyValue* kv = table_find(obj, key);
		if (kv == NULL)
			throw gcnew System::Exception("Could not find key");
		return Val(kv->value);
	}

	void Table::Add(uint key, Val val) {
		table_add(obj, key, val.val);
	}

	bool Table::Contains(uint key) {
		::KeyValue* kv = table_find(obj, key);
		return kv != NULL;
	}

	List<KeyValue>^ Table::KeyValues::get() {
		TableIter iter = table_iter_first(obj);
		List<KeyValue>^ r = gcnew List<KeyValue>();		
		while (table_iter_next(&iter)) 
			r->Add(KeyValue(&iter));
		return r;
	}

	/* Val functions */

	bool Val::IsInt::get() {
		return val_is_int(val) != 0;
	}

	int Val::ToInt() {
		return val_to_int(val);
	}

	float Val::ToFloat() {
		return val_to_flt(val);
	}

	CVM::Object Val::ToObject() {
		return CVM::Object(val_to_obj(val));
	}

	Table Val::ToTable() {
		return Table(val_to_tbl(val));
	}

	TypeFlags Val::Type::get() { 
		if (IsInt)
			return TypeFlags::Int;	
		return (TypeFlags)obj_get_type(val_to_obj(val));
	} 

	/* VM functions */

	VM::VM() { 
		ops = gcnew List<Op>(0);
		ks = gcnew List<CVM::Object>(0);
		vm = new ::VM();
		vm_init(vm); 
	}

	VM::~VM() { 
		delete vm;
		vm_free(vm); 
	}

	bool VM::Next() {
		return vm_next(vm) == 0; 
	}

	Op VM::CurOp::get() {
		return Op(vm->ip);
	}

	int VM::Count::get() { 
		return vm_stack_count(vm); 
	} 

	int VM::AuxCount::get() { 
		return vm_aux_stack_count(vm); 
	} 

	void VM::Run() { 
		vm_run(vm); 
	}

	void VM::Dispatch(byte code) { 
		vm_eval(vm, code); 
	}

	void VM::Load(array<byte>^ data) { 
		uint n = data->Length;
		assert(sizeof(Val**) == 4);
		if (n % 4 != 0)
			n += (4 - (n % 4));
		assert(n % 4 == 0);
		byte* buffer = (byte*)malloc(n);
		for (int i=0; i < data->Length; ++i)
			buffer[i] = data[i];
		::Val** pv = (::Val**)buffer;
		deserialize_vm(vm, pv);
		ops->Clear();
		ks->Clear();
		uint i = 0;
		while (i < vm->ccnt) {
			Op op(vm->code + i);
			i += op.NumBytes;
			ops->Add(op);
		};
		i = 0;
		n = array_count(&vm->constants);
		for (i=0; i < n; ++i) {
			::Val* k = array_at(&vm->constants, i);
			ks->Add(CVM::Object(val_to_obj(k)));
		}
		free(buffer);
	}

	List<CVM::Object>^ VM::GetHeapMemory() {
		::MemIter iter;
		List<CVM::Object>^ r = gcnew List<CVM::Object>();
		mem_foreach(&iter, &vm->mem)
			r->Add(CVM::Object(iter.obj));
		return r;
	}

	String^ VM::CodeString::get()
    {
		System::Text::StringBuilder^ sb = gcnew System::Text::StringBuilder();
        for (int i = 0; i < Ops->Count; ++i)
        {
            sb->Append(Ops[i].ToString());
            sb->Append(" ");
        }
        return sb->ToString();
    }

	List<Val>^ VM::Stack::get() { 
		List<Val>^ r = gcnew List<Val>();
		for (int i=0; i < Count; ++i)
			r->Add(At(i));
		return r;
	}

	List<Val>^ VM::AuxStack::get() { 
		List<Val>^ r = gcnew List<Val>();
		for (int i=0; i < AuxCount; ++i)
			r->Add(AuxAt(i));
		return r;
	}
}